{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import asyncio\n",
    "\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "from typing import Annotated\n",
    "from openai import AsyncOpenAI\n",
    "\n",
    "\n",
    "from semantic_kernel.kernel import Kernel\n",
    "from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n",
    "from semantic_kernel.agents import ChatCompletionAgent\n",
    "from semantic_kernel.contents import ChatHistory\n",
    "\n",
    "\n",
    "from semantic_kernel.agents.open_ai import OpenAIAssistantAgent\n",
    "from semantic_kernel.contents import AuthorRole, ChatMessageContent\n",
    "from semantic_kernel.functions import kernel_function\n",
    "\n",
    "from semantic_kernel.connectors.ai import FunctionChoiceBehavior\n",
    "\n",
    "from semantic_kernel.contents.function_call_content import FunctionCallContent\n",
    "from semantic_kernel.contents.function_result_content import FunctionResultContent\n",
    "from semantic_kernel.functions import KernelArguments, kernel_function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define a sample plugin for the sample\n",
    "class DestinationsPlugin:\n",
    "    \"\"\"A List of Destinations for vacation.\"\"\"\n",
    "\n",
    "    @kernel_function(description=\"Provides a list of vacation destinations.\")\n",
    "    def get_destinations(self) -> Annotated[str, \"Returns the specials from the menu.\"]:\n",
    "        return \"\"\"\n",
    "        Barcelona, Spain\n",
    "        Paris, France\n",
    "        Berlin, Germany\n",
    "        Tokyo, Japan\n",
    "        New York, USA\n",
    "        \"\"\"\n",
    "\n",
    "    @kernel_function(description=\"Provides available flight times for a destination.\")\n",
    "    def get_flight_times(\n",
    "        self, destination: Annotated[str, \"The destination to check flight times for.\"]\n",
    "    ) -> Annotated[str, \"Returns flight times for the specified destination.\"]:\n",
    "        flight_times = {\n",
    "            \"Barcelona\": [\"08:30 AM\", \"02:15 PM\", \"10:45 PM\"],\n",
    "            \"Paris\": [\"06:45 AM\", \"12:30 PM\", \"07:15 PM\"],\n",
    "            \"Berlin\": [\"07:20 AM\", \"01:45 PM\", \"09:30 PM\"],\n",
    "            \"Tokyo\": [\"11:00 AM\", \"05:30 PM\", \"11:55 PM\"],\n",
    "            \"New York\": [\"05:15 AM\", \"03:00 PM\", \"08:45 PM\"]\n",
    "        }\n",
    "\n",
    "        # Extract just the city name from input that might contain country\n",
    "        city = destination.split(',')[0].strip()\n",
    "\n",
    "        if city in flight_times:\n",
    "            times = \", \".join(flight_times[city])\n",
    "            return f\"Flight times for {city}: {times}\"\n",
    "        else:\n",
    "            return f\"No flight information available for {city}.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv()\n",
    "client = AsyncOpenAI(\n",
    "    api_key=os.getenv(\"GITHUB_TOKEN\"), base_url=\"https://models.inference.ai.azure.com/\")\n",
    "\n",
    "kernel = Kernel()\n",
    "kernel.add_plugin(DestinationsPlugin(), plugin_name=\"destinations\")\n",
    "\n",
    "service_id = \"agent\"\n",
    "\n",
    "chat_completion_service = OpenAIChatCompletion(\n",
    "    ai_model_id=\"gpt-4o-mini\",\n",
    "    async_client=client,\n",
    "    service_id=service_id\n",
    ")\n",
    "kernel.add_service(chat_completion_service)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "settings = kernel.get_prompt_execution_settings_from_service_id(\n",
    "    service_id=service_id)\n",
    "settings.function_choice_behavior = FunctionChoiceBehavior.Auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AGENT_NAME = \"TravelAgent\"\n",
    "AGENT_INSTRUCTIONS = \"\"\" \\\n",
    "\"You are Flight Booking Agent that provides information about available flights and gives travel activity suggestiosn when asked.\n",
    "Travel activity suggestions should be specific to customer, location and amount of time at location.\n",
    "\n",
    "You have access to the following tools to help users plan their trips:\n",
    "1. get_destinations: Returns a list of available vacation destinations that users can choose from.\n",
    "2. get_flight_times: Provides available flight times for specific destinations.\n",
    "\n",
    "Your process for assisting users:\n",
    "- When users inquire about flight booking, gather their preference ONLY on flight time. \n",
    "- When preferences are set, use get_flight_times to retrieve available flights.\n",
    "- Since you do not have access to a booking system. DO NOT ask to proceed with booking, just assume you have booked the flight.\n",
    "- Use any past conversation history to understand user preferences and consider them when making suggestions on flights and activites. When making a suggestion, be very clear on why you are making this suggestion if based on a user preference.\n",
    "Guidelines:\n",
    "- Use the exact destination names when using tools (Barcelona, Paris, Berlin, Tokyo, New York)\n",
    "- Respond in a helpful and enthusiastic manner about travel possibilities\n",
    "- Always seek feedback to ensure your suggestions meet the user's expectations\n",
    "- Acknowledge when a request falls outside your capabilities\n",
    "- For better formating, always display flight times in a list format\n",
    "- When giving any timed suggestions, refelect if the time frames are reasonable. Respond again if not. \n",
    "\n",
    "Your goal is to help users explore vacation options efficiently and make informed travel decisions by understanding their preferences and providing tailored recommendations.\n",
    "\"\"\"\n",
    "# Create the agent\n",
    "agent = ChatCompletionAgent(\n",
    "    service_id=service_id,\n",
    "    kernel=kernel,\n",
    "    name=AGENT_NAME,\n",
    "    instructions=AGENT_INSTRUCTIONS,\n",
    "    arguments=KernelArguments(settings=settings)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import display, HTML\n",
    "\n",
    "# Define the chat history outside of the function\n",
    "chat_history = ChatHistory()\n",
    "\n",
    "\n",
    "async def main():\n",
    "    # Respond to user input\n",
    "    user_inputs = [\n",
    "        \"Book me a flight to Barcelona\",\n",
    "        \"I prefer a later flight\",\n",
    "        \"That is too late, choose the earliest flight\",\n",
    "        \"I want to leave the same day, give me some suggestions of things to do in Barcelona during my layover if I take the last flight out\",\n",
    "        \"Are these the best tips given my layover time, can you provide a hourly iternary?\",\n",
    "        \"I am streesed this wont be enough time\"\n",
    "    ]\n",
    "\n",
    "    for user_input in user_inputs:\n",
    "        # Add the user input to the chat history\n",
    "        chat_history.add_user_message(user_input)\n",
    "\n",
    "        # Start building HTML output\n",
    "        html_output = f\"<div style='margin-bottom:10px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>User:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px'>{user_input}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "\n",
    "        agent_name: str | None = None\n",
    "        full_response = \"\"\n",
    "        function_calls = []\n",
    "\n",
    "        # Collect the agent's response with function call tracking\n",
    "        async for content in agent.invoke_stream(chat_history):\n",
    "            if not agent_name and hasattr(content, 'name'):\n",
    "                agent_name = content.name\n",
    "\n",
    "            # Track function calls and results\n",
    "            for item in content.items:\n",
    "                if isinstance(item, FunctionCallContent):\n",
    "                    call_info = f\"Calling: {item.function_name}({item.arguments})\"\n",
    "                    function_calls.append(call_info)\n",
    "                elif isinstance(item, FunctionResultContent):\n",
    "                    result_info = f\"Result: {item.result}\"\n",
    "                    function_calls.append(result_info)\n",
    "\n",
    "            # Add content to response if it's not a function-related message\n",
    "            if (hasattr(content, 'content') and\n",
    "                content.content and\n",
    "                content.content.strip() and\n",
    "                not any(isinstance(item, (FunctionCallContent, FunctionResultContent))\n",
    "                        for item in content.items)):\n",
    "                full_response += content.content\n",
    "\n",
    "        # Add function calls to HTML if any occurred\n",
    "        if function_calls:\n",
    "            html_output += f\"<div style='margin-bottom:10px'>\"\n",
    "            html_output += f\"<details>\"\n",
    "            html_output += f\"<summary style='cursor:pointer; font-weight:bold; color:#0066cc;'>Function Calls (click to expand)</summary>\"\n",
    "            html_output += f\"<div style='margin:10px; padding:10px; background-color:#f8f8f8; border:1px solid #ddd; border-radius:4px; white-space:pre-wrap;'>\"\n",
    "            html_output += \"<br>\".join(function_calls)\n",
    "            html_output += f\"</div></details></div>\"\n",
    "\n",
    "        # Add agent response to HTML\n",
    "        html_output += f\"<div style='margin-bottom:20px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>{agent_name or 'Assistant'}:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px; white-space:pre-wrap'>{full_response}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "        html_output += \"<hr>\"\n",
    "\n",
    "        # Display formatted HTML\n",
    "        display(HTML(html_output))\n",
    "\n",
    "        # Add the assistant's response to the chat history\n",
    "        chat_history.add_assistant_message(full_response)\n",
    "\n",
    "\n",
    "await main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import display, HTML\n",
    "\n",
    "# This will use the same chat_history that was defined earlier\n",
    "# chat_history is already defined outside the function scope\n",
    "\n",
    "\n",
    "async def continue_chat():\n",
    "    # Continue the conversation with new user inputs\n",
    "    user_inputs = [\n",
    "        \"Book me a flight to Paris\"\n",
    "    ]\n",
    "\n",
    "    for user_input in user_inputs:\n",
    "        # Add the user input to the chat history\n",
    "        chat_history.add_user_message(user_input)\n",
    "\n",
    "        # Start building HTML output\n",
    "        html_output = f\"<div style='margin-bottom:10px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>User:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px'>{user_input}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "\n",
    "        agent_name: str | None = None\n",
    "        full_response = \"\"\n",
    "        function_calls = []\n",
    "\n",
    "        # Collect the agent's response with function call tracking\n",
    "        async for content in agent.invoke_stream(chat_history):\n",
    "            if not agent_name and hasattr(content, 'name'):\n",
    "                agent_name = content.name\n",
    "\n",
    "            # Track function calls and results\n",
    "            for item in content.items:\n",
    "                if isinstance(item, FunctionCallContent):\n",
    "                    call_info = f\"Calling: {item.function_name}({item.arguments})\"\n",
    "                    function_calls.append(call_info)\n",
    "                elif isinstance(item, FunctionResultContent):\n",
    "                    result_info = f\"Result: {item.result}\"\n",
    "                    function_calls.append(result_info)\n",
    "\n",
    "            # Add content to response if it's not a function-related message\n",
    "            if (hasattr(content, 'content') and\n",
    "                content.content and\n",
    "                content.content.strip() and\n",
    "                not any(isinstance(item, (FunctionCallContent, FunctionResultContent))\n",
    "                        for item in content.items)):\n",
    "                full_response += content.content\n",
    "\n",
    "        # Add function calls to HTML if any occurred\n",
    "        if function_calls:\n",
    "            html_output += f\"<div style='margin-bottom:10px'>\"\n",
    "            html_output += f\"<details>\"\n",
    "            html_output += f\"<summary style='cursor:pointer; font-weight:bold; color:#0066cc;'>Function Calls (click to expand)</summary>\"\n",
    "            html_output += f\"<div style='margin:10px; padding:10px; background-color:#f8f8f8; border:1px solid #ddd; border-radius:4px; white-space:pre-wrap;'>\"\n",
    "            html_output += \"<br>\".join(function_calls)\n",
    "            html_output += f\"</div></details></div>\"\n",
    "\n",
    "        # Add agent response to HTML\n",
    "        html_output += f\"<div style='margin-bottom:20px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>{agent_name or 'Assistant'}:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px; white-space:pre-wrap'>{full_response}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "        html_output += \"<hr>\"\n",
    "\n",
    "        # Display formatted HTML\n",
    "        display(HTML(html_output))\n",
    "\n",
    "        # Add the assistant's response to the chat history\n",
    "        chat_history.add_assistant_message(full_response)\n",
    "\n",
    "\n",
    "# Run this function in a new cell to continue the conversation\n",
    "# with the same chat history\n",
    "await continue_chat()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
